package gu.sql2java.manager;

import static gu.sql2java.Constant.JdbcProperty.*;
import static gu.sql2java.SimpleLog.logString;
import static com.google.common.base.Preconditions.checkState;

import java.io.File;
import java.net.URL;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;

import gu.sql2java.Constant.JdbcProperty;

/**
 * SQLite initializer
 * @author guyadong
 *
 */
public class SqliteInitializer extends BaseEmbeddedInitializer {

	public SqliteInitializer(File db, URL createSql, boolean runInMemory) {
		super(db, createSql, runInMemory);
	}

	private static String createJDBCUrl(boolean runInMemory, File db) {
		String jdbcurl = null;
		if (runInMemory) {
			jdbcurl = String.format("jdbc:sqlite::memory:");
		} else {
			jdbcurl = String.format("jdbc:sqlite:%s", db.getAbsolutePath());
		}
		return jdbcurl;
	}
	
	@Override
	protected void doInitMemory() {
		String sqlStr = "restore from " + db.getAbsolutePath();
		Managers.getSqlRunner(dbprops.getProperty(JdbcProperty.ALIAS.key)).runSql(sqlStr);
	}
	
	@Override
	protected ScriptRunner normalize(ScriptRunner runner){
		return runner.addClearRegex(Pattern.compile("DEFAULT +CHARSET *= *(.+)",Pattern.CASE_INSENSITIVE))
				.addClearRegex(Pattern.compile("ON +UPDATE +CURRENT_TIMESTAMP",Pattern.CASE_INSENSITIVE))
				.addClearRegex(Pattern.compile("SET +NAMES.*",Pattern.CASE_INSENSITIVE))
				.addReplaceRegex(Pattern.compile("AUTO_INCREMENT",Pattern.CASE_INSENSITIVE),"AUTOINCREMENT")
				/** 替换SQL整数类型(如'INT(11)')为integer */
				.addReplaceRegex(Pattern.compile("int\\s*\\(\\s*\\d+\\s*\\)",Pattern.CASE_INSENSITIVE),"integer")
				.addIgnoreRegex(Pattern.compile("SET +NAMES.*",Pattern.CASE_INSENSITIVE))
				.addIgnoreRegex(Pattern.compile("SET +CHARACTER.*",Pattern.CASE_INSENSITIVE));
	}
	@Override
	protected void doPersist() {
		String sqlStr = "backup to " + db.getAbsolutePath();
		Managers.getSqlRunner(dbprops.getProperty(JdbcProperty.ALIAS.key)).runSql(sqlStr);
	}

	@Override
	protected void writeDbProps(Properties dbprops) {
		dbprops.setProperty(JDBC_URL.key, createJDBCUrl(runInMemory, db));
		dbprops.setProperty(DATASOURCE.key, "SQLITE");
	}

	@Override
	protected void checkExistsDatabse(File db) throws EmbeddedInitException {
		if(db.isFile() && db.canRead() && db.canWrite()){
			return;
		}
		throw new EmbeddedInitException(logString("{} IS NOT a SQLite database", db.getAbsolutePath()));

	}
	
	private List<String> getPrimaryKeys(DatabaseMetaData metadata,String tablename) throws SQLException{
		ResultSet resultSet = metadata.getPrimaryKeys("", "", tablename);
		List<String> pkNames = Lists.newLinkedList();
		while (resultSet.next()) {
			pkNames.add(resultSet.getString("COLUMN_NAME"));	
		}
		checkState(!pkNames.isEmpty(),"NOT FOUND Primary key of table  %s",tablename);
		return pkNames;
	}
	
	@Override
	protected List<String> afterCreateTable(ScriptRunner runner) throws SQLException{
		List<String> executableSqls = runner.getExecutableSqls();
		if(executableSqls.isEmpty()){
			return Collections.emptyList();
		}
		Connection connection = null;
		try {
			connection = Managers.managerInstanceOfAlias(dbprops.getProperty(JdbcProperty.ALIAS.key)).getConnection();
			DatabaseMetaData metadata = connection.getMetaData();			
			List<String> additional = Lists.newLinkedList();
			Pattern tablePattern = Pattern.compile("CREATE\\s+TABLE\\s+((\\w+\\s+)*)([\\w\\.\'\"`]+)\\s*\\((.+)\\)",Pattern.CASE_INSENSITIVE);
			// 为所有表类型为timestamp且更新策略为 ON UPDATE CURRENT_TIMESTAMP的字段创建触发器
			for(String sql:executableSqls){
				Matcher tmacher = tablePattern.matcher(sql);
				while(tmacher.find()){
					String tablename = tmacher.group(3);
					List<String> pks = getPrimaryKeys(metadata,tablename);
					String cols = tmacher.group(4);
					Pattern colPattern = Pattern.compile("([\'\"`])?([\\w\\d]+)\\1\\s+timestamp\\s+DEFAULT(\\s+(?:CURRENT_TIMESTAMP|NULL)\\s+)?ON\\s+UPDATE\\s+CURRENT_TIMESTAMP\\s*(,|$)",Pattern.CASE_INSENSITIVE);
					Matcher cmacher = colPattern.matcher(cols);
					while(cmacher.find()){					
						String timestampColumn = cmacher.group(2);
						String triggerName = "trigger_"+tablename + "_" + timestampColumn;
						String tmpl = 
								"CREATE TRIGGER IF NOT EXISTS ${trigger} "
										+ "AFTER  UPDATE "
										+ "ON ${table} "
										+ "FOR EACH ROW "
										+ "WHEN NEW.${col} <= OLD.${col} "
										+ "BEGIN  "
										+ "	UPDATE ${table} SET ${col}=CURRENT_TIMESTAMP WHERE ${where};"
										+ "END;"; 
						
						String where = Joiner.on(" AND ").join(Lists.transform(pks, new Function<String,String>(){
							@Override
							public String apply(String input) {
								return "NEW.{}=OLD.{}".replace("{}", input);
							}}));
						
						String triggerSQL = tmpl.replace("${table}", tablename)
								.replace("${col}", timestampColumn)
								.replace("${trigger}", triggerName)
								.replace("${where}", where);
//						SimpleLog.log("create trigger " + triggerName);
//						SimpleLog.log(triggerSQL);
						additional.add(triggerSQL);
					}
				}
			}
			return additional;
		} finally{
			if(null != connection){
				connection.close();
			}
		}
	}
	
	/**
	 * SQLite数据库初始化
	 * @param db 数据文件位置
	 * @param createSql 数据库建表语句(SQL)位置
	 * @param runInMemory 为{@code true}以内存方式运行
	 * @param properties 附加的配置参数
	 * @return {@link SqliteInitializer}实例
	 */
	public static SqliteInitializer init(File db, URL createSql, boolean runInMemory, Properties properties){
		return init(SqliteInitializer.class, db, createSql, runInMemory, properties);
	}
	/**
	 * SQLite数据库初始化
	 * @param db 数据文件位置(File)
	 * @param createSqlURL 数据库建表语句(SQL)位置(URL)
	 * @param runInMemory 为{@code true}以内存方式运行
	 * @param properties 附加的配置参数
	 * @return {@link SqliteInitializer}实例
	 */
	public static SqliteInitializer init(String db, String createSqlURL, boolean runInMemory, Properties properties){
		return init(SqliteInitializer.class, db, createSqlURL, runInMemory, properties);
	}
}
